import { watch } from 'vue';
import {
  isString, noop, resolveUnref, tryOnScopeDispose, unrefElement
} from './shared';

export function useEventListener(...args) {
  let target; // 目标对象 可不传 默认为window 或者 VueInstance | HTMLElement | SVGElement
  let events; // 触发事件 可多个
  let listeners; // 触发事件对应的function
  let options; // EventListener options 配置
  // 第一个参数为数组或者string target默认取 window
  if (isString(args[0]) || Array.isArray(args[0])) {
    [ events, listeners, options ] = args;
    target = window;
  } else {
    [ target, events, listeners, options ] = args;
  }
  // 无 target 返回一个空函数
  if (!target) return noop;

  if (!Array.isArray(events)) events = [ events ];
  if (!Array.isArray(listeners)) listeners = [ listeners ];

  const cleanups = [];
  // 清除所有的监听事件
  const cleanup = () => {
    cleanups.forEach((fn) => fn());
    cleanups.length = 0;
  };

  // 注册一个EventListener 返回一个可销毁此EventListener的方法
  function register(el, event, listener, option) {
    el.addEventListener(event, listener, option);
    return () => el.removeEventListener(event, listener, option);
  }

  // 监听target 和 options 变化 == 重新侦听
  // watch返回一个停止此监听的方法
  const stopWatch = watch(
    () => [ unrefElement(target), resolveUnref(options) ],
    ([ el, option ], oldValue) => {
      console.log(events.toString(), `${oldValue ? '重新' : ''}注册侦听器 by useEventListener！`);
      cleanup();
      if (!el) return;
      cleanups.push(
        // map返回新数组 使用flatMap二维拍成一维数组
        // ?? 如果多个events 那会同一个类型会注册两个事件 ??
        ...events.flatMap((event) => (listeners).map((listener) => register(el, event, listener, option)))
      );
    },
    // immediate：立即触发回调。
    // flush：调整回调函数的刷新时机 'post' 指在更新dom后。
    { immediate: true, flush: 'post' }
  );
    // 清空侦听器和Watch
  const stop = () => {
    console.log(events.toString(), '我被注销了！！');
    stopWatch();
    cleanup();
  };

  // 当组件注销时 onScopeDispose回调函数中调用 stop方法
  // ?? 当手动触发调用stop后 组件注销时也会调用 stop方法 ??
  tryOnScopeDispose(stop);
  return stop;
}
